Python3によるオブジェクト指向プログラミング - オブジェクトと型
著者:Leonardo Giordani - 20/08/2014 Updated on May 22, 2019
この記事はIPython Notebookとして公開 されています。 このシリーズについて
オブジェクト指向プログラミング(OOP: Object Oriented Programming) は、60年代の初期の試みから、現在使用されている最も重要な言語のいくつかに至るまで、数十年にわたって主要なプログラミング・パラダイムとなっています。プログラミングの概念や設計手法の集合体であるOOPは、ある言語で「正しく」「完全に」実装されているとは言えません。実際、言語の数だけ実装が存在します。
そのため、OOP言語の最も興味深い点の1つは、これらのコンセプトをどのように実装しているかを理解することです。この記事では、Python言語のOOP実装の分析を始めようと思います。しかし、トピックが豊富なので、この試みは、Python初心者がこの美しい(そして時に独特な)言語への道を見つけようとするための思考のセットのようなものだと考えています。
この一連の投稿は、オブジェクト指向プログラミングの概念のPython 3の実装を読者に紹介することを目的としています。しかし、この記事と次の記事の内容は、以前の "OOP Concepts in Python 2.x "シリーズの内容と完全に異なるものではありません。というのも、Python 3はPython 2の進化形であり、新しい言語ではないため、内部構造のいくつかは大きく変わっても、全体的な哲学は変わらないからです。
そこで私は、単なる修正点のリストを掲載するのではなく、以前のシリーズを分割して、内容をPython 3に合わせることにしました。この方法は、前シリーズを読まなければならないような新しい読者にとって、より有用であると思います。
print
Python 3で導入された最も顕著な変更点の1つは、printキーワードが print( )関数に変わったことです。これは、内部構造に加えられた他の変更に比べて非常に小さな変更ですが、最も視覚的に目を引くものであり、Python 3のコードを書き始めたときに、構文エラーの80%の原因となるでしょう。
printは関数になったので、print aではなくprint(a)と書くことを覚えておいてください。
オブジェクトに戻ろう
コンピュータサイエンスはデータと、そのデータを操作するための手順を扱います。初期のFortranプログラムから最新のモバイルアプリまで、すべてがデータとその操作に関するものです。
データが材料であり、手続きがレシピであるならば、これらを別々に扱うことは合理的であるように思えます(そうすることもできます)。
Pythonで手続き型プログラミングをしてみよう
code: python
# これは何かしらのデータ
data = (13, 63, 5, 378, 58, 40)
# 平均値を計算するプロシージャ
def avg(d):
return sum(d)/len(d)
print(avg(data))
ご覧のように、このコードはまったく問題がなく、一般的なものです。プロシージャ(procedure)(手順や手続き、この場合は関数)は一連のデータを操作し、一連の項目の平均を返します。ここまではいいのですが、ある数字の平均を計算すると、その数字はそのままで、新しいデータが作成されます。
しかし、日常の世界を観察すると、複雑なデータは突然変異することがわかります。電気機器の電源が入ったり切れたり、ドアが開いたり閉じたり、新しい本を買うと部屋の本棚の中身が変わったり。
それでも、データと手順を分けて管理することは可能です。
code: python
# これらデータは2つの番号のついたドアを表し、最初は閉じられている
# ドアを開けるするプロシージャ
def open_door(door):
open_door(door1)
print(door1)
ドアを番号とドアの状態を含む構造体として説明しました(例えばLISPなどの言語で行うように)。プロシージャはこの構造がどのように作られているかを知っていて、それを変更することができます。
これも魅力的に機能します。しかし、特殊なタイプのデータを作り始めると、いくつかの問題が発生します。例えば、鍵がかかっていないときだけ開けることができる「鍵付きドア」というデータ型を導入したらどうなるでしょうか?では、次のように考えてみましょう。
code: python
# これらデータは2つの普通のドアを表し、最初は閉じられている
# このデータは鍵付きのドアを表し、最初は閉じられていて、鍵がかけられている
# 普通のドアを開けるプロシージャ
def open_door(door):
# 鍵付きのドアを開けるプロシージャ
def open_ldoor(door):
open_door(door1)
print(door1)
open_ldoor(ldoor1)
print(ldoor1)
このコードには何の驚きもなく、すべてが機能しています。しかし、ご覧のように、ロックされたドアを開けるプロシージャの実装が、通常のドアを開けるプロシージャと異なるため、別の名前を見つけなければなりませんでした。でも、ちょっと待ってください。ドアを開けることに変わりはないし、動作も同じで、ドア自体の状態が変わるだけです。では、動詞が同じなのに、なぜ、ロックされたドアは open_door() ではなく、open_ldoor() を使って開けなければならないのでしょうか?
このように、データとプロシージャの分離は、状況によっては完全には適合しない可能性があります。重要な問題は、"open " アクションが実際にドアを使用しているのではなく、ドアの状態を変更しているということです。つまり、携帯電話の音量調節ボタンが携帯電話にあるように、「開く」という手順は「ドア」というデータにこだわるべきなのです。
これはまさに、オブジェクト(Object) という概念につながるものです。OOPにおけるオブジェクトとは、データを保持する構造体と、それを操作するプロシージャのことです。
タイプはどうですか?
データの話をするときには、すぐに型(Type)という概念を導入する必要があります。この概念には、コンピュータ・サイエンスにおいて言及する価値のある2つの意味があります:行動的(behavioural)な意味と構造的(structural)な意味です。
行動的な意味とは、何かがどのように振る舞うのかを記述することで、何かが何であるかを知ることができるという事実を表しています。これは、いわゆる「ダック・タイピング」(ここでの「タイピング」は「タイプ(型付け)する」という意味で、「キーボードをタイプ(打つ)」という意味ではありません)の基礎となっています。
訳注:ダック・タイピング(Duck Typing)
ダック・タイピングは、Pythonなどの動的型付けオブジェクト指向プログラミング言語に特徴的な型付けの作法のことです。それらの言語では、オブジェクト(変数の値)に何ができるかはオブジェクトそのものが決定します。これによりポリモーフィズム(多態性)を実現することができます。
構造的な意味では、何かの内部構造を見て、その型(タイプ)を特定します。つまり、同じように動作するが内部的には異なる2つのものは、異なるタイプであるということです。
どちらの視点も有効であり、言語によって「type」のどちらかの意味を実装したり、強調したりすることがあります。
クラスゲーム
Pythonのオブジェクトは、クラス(Class) を使ってその構造を記述することができます。クラスとは、"a book"、"a car"、"a door "などの一般的なオブジェクトをプログラミングで表現したものです。"a door "について話すとき、部屋の中の特定のドアを参照しなくても、誰もが私の言っていることを理解できます。
Pythonでは、オブジェクトのタイプは、そのオブジェクトを構築するために使用されたクラスによって表されます。つまり、Pythonでは、タイプという言葉は、クラスという言葉と同じ意味を持っています。
例えば,Pythonの組み込みクラスの1つにintがあり,これは整数を表すものです。
code: python
>> a = 6
>> print(a)
6
>> print(type(a))
<class 'int'>
>> print(a.__class__)
<class 'int'>
ご覧のように、組み込み関数 type() は、魔法の属性 __class__ の内容を返します(ここでの魔法とは、その値が舞台裏でPython自身によって管理されていることを意味します)。変数 a の型、つまりそのクラスは int です(これはこのかなり複雑なトピックについての非常に不正確な説明ですので、今の段階では表面をなぞっているだけであることを覚えておいてください)。
クラスがあると、そのクラスをインスタンス化して、その型の具体的なオブジェクト(インスタンス(Instance))を得ることができます。クラスをインスタンス化するPythonの構文は、関数の呼び出しと同じです。
code: python
>> b = int()
>> type(b)
<class 'int'>
インスタンスを作成する際には、クラスの定義に従っていくつかの値を渡して、インスタンスを初期化することができます。
code: python
>> b = int()
>> print(b)
0
>> c = int(7)
>> print(c)
7
この例では、intクラスは、引数なしで呼ばれた場合は値0の整数を作成し、そうでない場合は与えられた引数を使って新しく作成されたオブジェクトを初期化します。
最初に説明したときの手続き型の例に合わせて,ドアを表すクラスを書いてみましょう。
code: python
class Door:
def __init__(self, number, status):
self.number = number
self.status = status
def open(self):
self.status = 'open'
def close(self):
self.status = 'closed'
class キーワードは、Door という名前の新しいクラスを定義します。class の下にインデントされているものはすべて、このクラスの一部です。オブジェクトの内部で記述される関数はメソッド(Method) と呼ばれ、標準的な関数と何ら変わるところはありません。
クラスのメソッドは、第一引数としてself という特別な値を受け入れなければなりません(この名前は慣例ですが、決して破ってはいけません)。
クラスには __init__() という特別なメソッドを与えることができます。このメソッドはクラスがインスタンス化されたときに実行され、クラスを呼び出すときに渡された引数を受け取ります。OOPでは、このようなメソッドの一般的な名前はコンストラクタ(Constructor) です。
self.number と self.status 変数はオブジェクトの属性(アトリビュート:Attribute) と呼ばれます。Pythonでは、メソッドと属性はともにオブジェクトのメンバー(Member) であり、ドット構文でアクセスできます。属性とメソッドの違いは、後者が呼び出し可能(Callable) であることです(Pythonの用語では、メソッドは呼び出し可能であると言います)。
訳注: ドット構文(Dot Syntax)
オブジェクトAの属性dataにアクセスするために、A.data のように記述すること。
ドット表記(Dot notation)とも言われます。
見ての通り、__init__()メソッドは属性を生成して初期化します。なぜなら属性は他の場所で宣言されていないからです。これはPythonでは非常に重要なことで、Pythonが変数の型を扱う方法と厳密に関連しています。ポリモーフィズムを扱う際のこれらの概念については、後の記事で詳しく説明します。
クラスは具体的なオブジェクトを作成するために使用することができます。
code: python
>> door1 = Door(1, 'closed')
>> type(door1)
<class '__main__.Door'>
>> print(door1.number)
1
>> print(door1.status)
closed
これで door1 は Door クラスのインスタンスになりました。type() はクラスを __main__.Door として返します。これはクラスが対話型シェルで直接定義されているからで、つまり現在のメインモジュールで定義されているからです。
オブジェクトのメソッドを呼び出す、つまりオブジェクトの内部関数を実行するには、ドット構文で属性としてアクセスし、標準的な関数のように呼び出します。
code: python
>> door1.open()
>> print(door1.number)
1
>> print(door1.status)
open
このケースでは、door1インスタンスの open() メソッドが呼び出されています。open() メソッドには引数が渡されていませんが、クラス宣言を確認すると、引数(self)を受け取るように宣言されていることがわかります。インスタンスのメソッドを呼び出すと、Pythonは自動的にインスタンス自身を第1引数としてメソッドに渡します。
インスタンスは必要な数だけ作ることができ、それらはお互いに全く関係ありません。つまり、あるインスタンスを変更しても、同じクラスの別のインスタンスには反映されません。
要約
オブジェクトはクラスによって記述され、クラスは互いに無関係な1つ以上のインスタンスを生成することができます。クラスには関数であるメソッドが含まれており、メソッドは少なくとも1つのselfという引数を受け取りますが、これはメソッドが呼び出された実際のインスタンスです。特別なメソッドである__init__() は、オブジェクトの初期化を行い、属性の初期値を設定します。
映画のトリビア
セクションのタイトルは以下の映画に由来しています
Back to the Future (1985) : Back to the Object / オブジェクトに戻ろう
What About Bob? (1991):Waht About Type? / タイプはどうですか?
ちなみにこのコメディー映画の邦題は「おつむて・ん・て・ん・クリニック」という意味不明なものです。
Wargames(1983):Class Games / クラスゲーム
参考資料
Python3によるオブジェクト指向プログラミングシリーズ
日本語訳:Python3によるオブジェクト指向プログラミング - オブジェクトと型